#include "Memory.hpp"
#include "Error.hpp"

#include <string.h>

#ifdef UNITTEST
#include <iostream>
#include <iomanip>
#endif

#if MEMORY_MODE == MEMORY_MODE_GLOBAL
unsigned int Memory::_memorySize = 0;
unsigned int Memory::_lastFreeBlock = 0;
unsigned char* Memory::_memory = NULL;
#endif

Memory::Memory ( const unsigned int maxSize )
{
#if MEMORY_MODE == MEMORY_MODE_GLOBAL
    if ( _memory == NULL )
    {
        _memory = new unsigned char [ 4096 ];
        _memorySize = 4096;
    }
    _saveLastFreeBlock = _lastFreeBlock;
#else
    _memorySize = maxSize;
    _lastFreeBlock = 0;
	_memory = new unsigned char [ maxSize ];
#endif
}

Memory::~Memory ()
{
#if MEMORY_MODE == MEMORY_MODE_GLOBAL
    _lastFreeBlock = _saveLastFreeBlock;
#else
	delete [] _memory;
#endif
}

unsigned int
Memory::allocate ( const unsigned int size )
{
	if ( _memorySize < _lastFreeBlock + size )
		throw Error ( MEMORY_NOMORESPACE, "No more free space for your allocation", MEMORY_LEVEL_ERROR );
	unsigned int address = _lastFreeBlock;
	_lastFreeBlock += size;
	_addressToSize [ address ] = size;
	return address;
}

void
Memory::set ( const unsigned int address, const void* data, const unsigned int size )
{
	if ( address >= _lastFreeBlock || address + size > _lastFreeBlock )
		throw Error ( MEMORY_WRITEONNONALLOCATED, "write in unallocated memory", MEMORY_LEVEL_ERROR );
	memcpy ( _memory + address, data, size );
}

void
Memory::get ( const unsigned int address, void* data, const unsigned int size ) const
{
	if ( address >= _lastFreeBlock || address + size > _lastFreeBlock )
		throw Error ( MEMORY_READONNONALLOCATED, "read in unallocated memory", MEMORY_LEVEL_ERROR );
	memcpy ( data, _memory + address, size );
}

#ifdef UNITTEST
void
Memory::unitaryTest ()
{
	int tmp =128;
	std::clog << "Tests unitaires de la classe Memory" << std::endl;
	std::clog << "Allocation de la memoire" << std::endl;
	Memory* memoire = new Memory ( 1024 );
	std::clog << "-----------  Tests d'operations valides" << std::endl;
	try
	{
		std::clog << "Allocation d'un entier";
		unsigned int ad1 = memoire->allocate ( sizeof ( int ) );
		std::clog << " ok" << std::endl;
		std::clog << "Allocation bourrine";
		unsigned int ad2 = memoire->allocate ( 256 );
		std::clog << " ok" << std::endl;
		std::clog << "ecriture memoire";
		memoire->set ( ad1, (void*) & tmp, sizeof ( int ) );
		std::clog << " ok" << std::endl;
		std::clog << "lecture memoire";
		memoire->get ( ad2, (void*) & tmp, sizeof ( int ) );
		std::clog << " ok" << std::endl;
	}
	catch ( Error& e )
	{
		std::cerr << e.what () << std::endl;
	}
	std::clog << "-----------  Tests d'operations invalides" << std::endl;
	try {
		std::clog << "allocation memoire trop grande" << std::endl;
		memoire->allocate ( 1024 );
	}
	catch ( Error& e )
	{
		std::cerr << e.what () << std::endl;
	}
	try {
		std::clog << "ecriture memoire dans une zone non allouee" << std::endl;
		memoire->set ( 512, (void*) & tmp, 4 );
	}
	catch ( Error& e )
	{
		std::cerr << e.what () << std::endl;
	}
	try {
		std::clog << "lecture memoire depuis une zone non allouee" << std::endl;
		memoire->get ( 512, (void*) & tmp, 4 );
	}
	catch ( Error& e )
	{
		std::cerr << e.what () << std::endl;
	}
	std::clog << "-----------" << std::endl;
	std::clog << "Liberation de la memoire" << std::endl;
	delete memoire;
}
#endif
